package com.chinacache.maint.commons.orm.hibernate;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.PropertyUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.metadata.ClassMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import com.chinacache.maint.commons.util.BeanUtils;

/**
 * 通用的HibernateDAO 基类.
 * 
 * 本类可以在service类中直接使用，透过构造函数SimpleHibernateDao(SessionFactory sessionFactory,
 * Class<T> entityClass)构造 也可以继承出DAO子类，在多个Service类中共享数据库操作.
 * 
 * 参考Spring2.5自带的Petlinc例子,取消了HibernateTemplate,直接使用sessionFactory.getCurrentSession().
 * 
 * @param <T>
 *            DAO操作的对象类型
 * @param <PK>
 *            主键类型
 * 
 * @author calvin
 */
@SuppressWarnings("unchecked")
public class SimpleHibernateDao<T, PK extends Serializable> {

	private SessionFactory sessionFactory;

	protected final Logger logger = LoggerFactory.getLogger(getClass());

	protected Class<T> entityClass;

	public SimpleHibernateDao(SessionFactory sessionFactory,
			Class<T> entityClass) {
		this.sessionFactory = sessionFactory;
		this.entityClass = entityClass;
	}

	public Session getSession() {
		return sessionFactory.getCurrentSession();
	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public void save(T entity) {
		Assert.notNull(entity);
		getSession().saveOrUpdate(entity);
	}

	public void delete(T entity) {
		Assert.notNull(entity);
		getSession().delete(entity);
	}

	public void delete(final PK id) {
		Assert.notNull(id);
		getSession().delete(findById(id));
	}

	public void update(T entity) {
		Assert.notNull(entity);
		getSession().update(entity);
	}

	public List<T> findAll() {
		return findByCriteria();
	}

	public Page<T> findAll(Page<T> page) {
		return findByCriteria(page);
	}

	public List<T> find(String queryString, Object... values) {
		return createQuery(queryString, values).list();
	}

	public Page<T> find(Page<T> page, String queryString, Object... values) {
		Query q = createQuery(queryString, values);
		if (page != null) {
			q.setFirstResult(page.getStart());
			q.setMaxResults(page.getPageSize());
		}
		page.setList(q.list());
		return page;
	}

	public Object findUnique(String queryString, Object... values) {
		return createQuery(queryString, values).uniqueResult();
	}

	public Integer findInt(String queryString, Object... values) {
		return (Integer) findUnique(queryString, values);
	}

	public Long findLong(String queryString, Object... values) {
		return (Long) findUnique(queryString, values);
	}

	public T findById(final PK id) {
		return (T) getSession().load(entityClass, id);
	}

	public List<T> findByCriteria(Criterion... criterion) {
		return createCriteria(criterion).list();
	}

	public Page<T> findByCriteria(Page page, Criterion... criterion) {
		Criteria c = createCriteria(criterion);
		pageCriteria(c, page);
		page.setList(c.list());
		return page;
	}

	public Page<T> findByCriteria(Page page, String orderBy, boolean isAsc,
			Criterion... criterion) {
		Criteria c = createCriteria(orderBy, isAsc, criterion);
		pageCriteria(c, page);
		page.setList(c.list());
		return page;
	}

	public List<T> findByProperty(String propertyName, Object value) {
		Assert.hasText(propertyName);
		return createCriteria(Restrictions.eq(propertyName, value)).list();
	}

	public T findUniqueByProperty(String propertyName, Object value) {
		Assert.hasText(propertyName);
		return (T) createCriteria(Restrictions.eq(propertyName, value))
				.uniqueResult();
	}

	/**
	 * 根据查询函数与参数列表创建Query对象,后续可进行更多处理.
	 */
	public Query createQuery(String queryString, Object... values) {
		Query queryObject = getSession().createQuery(queryString);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				queryObject.setParameter(i, values[i]);
			}
		}
		return queryObject;
	}

	public void deleteQuery(String queryString) {
		getSession().delete(queryString);
	}

	/**
	 * 根据Criterion条件创建Criteria,后续可进行更多处理.
	 */
	public Criteria createCriteria(Criterion... criterions) {
		Criteria criteria = getSession().createCriteria(entityClass);
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		return criteria;
	}

	/**
	 * 根据Criterion条件及排序字段创建Criteria,后续可进行更多处理.
	 */
	public Criteria createCriteria(String orderBy, boolean isAsc,
			Criterion... criterions) {
		Assert.hasText(orderBy);

		Criteria criteria = createCriteria(criterions);

		if (isAsc) {
			criteria.addOrder(Order.asc(orderBy));
		} else {
			criteria.addOrder(Order.desc(orderBy));
		}

		return criteria;
	}

	/**
	 * 对Criteria进行分页相关操作,包括查出总记录数,设定分页参数
	 */
	public void pageCriteria(Criteria c, Page page) {
		CriteriaImpl impl = (CriteriaImpl) c;

		// 先把Projection和OrderBy条件取出来,清空两者来执行Count操作
		Projection projection = impl.getProjection();
		List<CriteriaImpl.OrderEntry> orderEntries;
		try {
			orderEntries = (List) BeanUtils.forceGetProperty(impl,
					"orderEntries");
			BeanUtils.forceSetProperty(impl, "orderEntries", new ArrayList());
		} catch (Exception e) {
			throw new InternalError(" Runtime Exception impossibility throw ");
		}

		// 执行查询
		int totalCount = (Integer) c.setProjection(Projections.rowCount())
				.uniqueResult();
		if (totalCount < 1)
			return;

		// 将之前的Projection和OrderBy条件重新设回去
		c.setProjection(projection);
		if (projection == null) {
			c.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
		}

		try {
			BeanUtils.forceSetProperty(impl, "orderEntries", orderEntries);
		} catch (Exception e) {
			throw new InternalError(" Runtime Exception impossibility throw ");
		}

		page.setTotalCount(totalCount);

		c.setFirstResult(page.getStart()).setMaxResults(page.getPageSize());
	}

	/**
	 * 判断对象某些属性的值在数据库中是否唯一.
	 * 
	 * @param uniquePropertyNames
	 *            在POJO里不能重复的属性列表,以逗号分割 如"name,loginid,password"
	 */
	public boolean isUnique(Object entity, String uniquePropertyNames) {
		Assert.hasText(uniquePropertyNames);
		Criteria criteria = createCriteria().setProjection(
				Projections.rowCount());
		String[] nameList = uniquePropertyNames.split(",");
		try {
			// 循环加入唯一列
			for (String name : nameList) {
				criteria.add(Restrictions.eq(name, PropertyUtils.getProperty(
						entity, name)));
			}

			// 以下代码为了如果是update的情况,排除entity自身.
			String idName = getIdName(entityClass);

			// 取得entity的主键值
			PK id = getId(entity);

			// 如果id!=null,说明对象已存在,该操作为update,加入排除自身的判断
			if (id != null) {
				criteria.add(Restrictions.not(Restrictions.eq(idName, id)));
			}
		} catch (Exception e) {
			ReflectionUtils.handleReflectionException(e);
		}
		return (Integer) criteria.uniqueResult() == 0;
	}

	/**
	 * 取得对象的主键值,辅助函数.
	 */
	public PK getId(Object entity) throws NoSuchMethodException,
			IllegalAccessException, InvocationTargetException {
		Assert.notNull(entity);
		return (PK) PropertyUtils.getProperty(entity, getIdName(entityClass));
	}

	/**
	 * 取得对象的主键名,辅助函数.
	 */
	public String getIdName(Class clazz) {
		Assert.notNull(clazz);
		ClassMetadata meta = sessionFactory.getClassMetadata(clazz);
		Assert.notNull(meta, "Class " + clazz
				+ " not define in hibernate session factory.");
		String idName = meta.getIdentifierPropertyName();
		Assert.hasText(idName, clazz.getSimpleName()
				+ " has no identifier property define.");
		return idName;
	}
}